<!DOCTYPE html>


<html lang="zh-CN">
  

    <head>
      <meta charset="utf-8" />
        
      <meta name="description" content="Java、MySql、Spring、JVM、虚拟机、数据库、Docker、Redis" />
      
      <meta
        name="viewport"
        content="width=device-width, initial-scale=1, maximum-scale=1"
      />
      <title>深入理解JVM（八）类加载机制 |  ACEXS个人博客</title>
  <meta name="generator" content="hexo-theme-ayer">
      
      <link rel="shortcut icon" href="/blog-page/favicon.ico" />
       
<link rel="stylesheet" href="/blog-page/dist/main.css">

      <link
        rel="stylesheet"
        href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css"
      />
      
<link rel="stylesheet" href="/blog-page/css/custom.css">
 
      <script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>
       
 

      <link
        rel="stylesheet"
        href="https://cdn.jsdelivr.net/npm/@sweetalert2/theme-bulma@5.0.1/bulma.min.css"
      />
      <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.0.19/dist/sweetalert2.min.js"></script>

      <!-- mermaid -->
      
      <style>
        .swal2-styled.swal2-confirm {
          font-size: 1.6rem;
        }
      </style>
    <link rel="alternate" href="/blog-page/atom.xml" title="ACEXS个人博客" type="application/atom+xml">
</head>
  </html>
</html>


<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-深入理解JVM（八）类加载机制"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  深入理解JVM（八）类加载机制
</h1>
 

      
    </header>
     
    <div class="article-meta">
      <a href="/blog-page/2021/10/28/cd70ccfc5667.html" class="article-date">
  <time datetime="2021-10-28T07:58:14.000Z" itemprop="datePublished">2021-10-28</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/blog-page/categories/%E8%AF%BB%E4%B9%A6%E6%88%90%E8%AF%97/">读书成诗</a> / <a class="article-category-link" href="/blog-page/categories/%E8%AF%BB%E4%B9%A6%E6%88%90%E8%AF%97/JVM/">JVM</a>
  </div>
  
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> 字数统计:</span>
            <span class="post-count">5.4k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> 阅读时长≈</span>
            <span class="post-count">19 分钟</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <h1 id="类加载时机"><a href="#类加载时机" class="headerlink" title="类加载时机"></a>类加载时机</h1><p>《Java虚拟机规范》中没有强制约束，类加载过程中的加载、验证、准备、解析等阶段什么时候开始，但是严格规定了初始化执行的时机。前面几个步骤需要在初始化之前开始。</p>
<h2 id="有且仅有六种情况，必须立即对类进行初始化："><a href="#有且仅有六种情况，必须立即对类进行初始化：" class="headerlink" title="有且仅有六种情况，必须立即对类进行初始化："></a>有且仅有六种情况，必须立即对类进行初始化：</h2><ol>
<li>遇到new、getstatic、putstatic、或invokestatic这四个字节码指令时，如果类型没有进行初始化，则需要先触发其初始化阶段。场景有：<ul>
<li>使用new关键字实例化对象的时候。</li>
<li>读取或设置一个类型的静态字段（被final修饰、已在编译期把结果放入常量池的静态字段除外）的时候</li>
<li>调用一个类型的静态方法的时候</li>
</ul>
</li>
<li>使用java.lang.reflect包的方法对类型进行反射的时候，如果类型没有进行初始化，则需要先触发其初始化。</li>
<li>当初始化类的时候，如果发现其父类还没进行初始化，需要先触发其弗雷德初始化。</li>
<li>当虚拟机启动时，用户需要指定一个要执行的主类（包含main()方法的那个类）虚拟机会先初始化这个类</li>
<li>当使用JDK7新加入的动态语言支持时，如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄，并且这个方法句柄对应的类没有进行初始化，则需要先触发其初始化</li>
<li>当一个接口中定义了JDK8新加入的默认方法（被default关键字修饰的接口方法）时，如果这个接口实现类发生了初始化，那该接口要在其之前进行初始化</li>
</ol>
<p><strong>以上六种场景的行为，称为对一个类型进行<em>主动引用</em>。除此之外，其他引用类型的方式都称为<em>被动引用</em>。被动引用不会触发初始化。</strong></p>
<h2 id="特殊说明"><a href="#特殊说明" class="headerlink" title="特殊说明"></a>特殊说明</h2><ul>
<li>对静态字段，只有直接定义这个字段的类才会被初始化，通过子类引用父类中定义的静态字段，只会触发弗雷德初始化，而不会触发子类的初始化。</li>
<li>使用目标类型创建数组时，不会触发目标类型的初始化。初始化的类为虚拟机自动生成的，继承自java.lang.Object。且创建动作由newarray触发。</li>
<li>使用final 修饰的字段为常量，常量在编译阶段会存入调用类的常量池，本质上，没有直接引用到定义常量的类。因此不会触发定义常量的类的初始化。</li>
<li>接口也有初始化过程，并且编译器也会为接口生成“&lt;clinit&gt;()”类构造器，用于初始化接口中所定义的成员变量。唯一不同的是，接口在初始化过程中，不要求其父接口也要完成初始化。只有真正在使用到父接口的时候（如引用接口中定义的常量），才会初始化。</li>
</ul>
<h1 id="类加载过程"><a href="#类加载过程" class="headerlink" title="类加载过程"></a>类加载过程</h1><img src="/blog-page/2021/10/28/cd70ccfc5667/class_load.jpg" class="" width="540" title="This is class_load.jpg">

<h2 id="加载"><a href="#加载" class="headerlink" title="加载"></a>加载</h2><p><strong>类加载过程中要完成的第一个阶段，主要完成以下三件事：</strong></p>
<ol>
<li>通过一个类的全限定名来获取定义此类的二进制字节流。</li>
<li>将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。</li>
<li>在内存中生成一个代表这个类的java.lang.Class对象，作为方法区这个类的各个数据的访问入口。</li>
</ol>
<p><strong>数组与非数组类型加载的区别：</strong></p>
<p>非数组类型的加载阶段（准确说是加载阶段中获取类的二进制字节流的动作）是开发人员可控性最强的阶段。可以使用Java虚拟机内置的引导类加载器来完成，也可以自定义类加载器完成。对于数组而言，数组本身不通过类加载器创建，是由Java虚拟机直接在内存中动态构造出来的，但数组类与类加载器仍然有很密切关系，因为数组的元素类型最终还是需要类加载器来完成加载的。</p>
<p><strong>加载完成：</strong></p>
<p>加载阶段结束后，Java虚拟机外部的二进制字节流就按照虚拟机所设定的格式存储在方法区中了。类型数据妥善安置后，会在Java堆内存中实例化一个java.lang.Class类的对象，这个对象将作为程序访问方法区中类型数据的外部接口。</p>
<p><strong>后续：</strong></p>
<p>加载阶段与连接阶段的不放呢动作可能是交叉进行的，但两个阶段的开始时间仍保持着固定的先后顺序。</p>
<h2 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h2><p>连接阶段的第一步，目的是为了确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求，保证这些信息被当作代码运行后不会危害虚拟机自身的安全。</p>
<p><strong>必要性：</strong></p>
<p>Java代码本身无法做到访问数组边界外的数据，将对象转型为它并未实现的类型，跳转到不存在的代码行之类的事情。尝试这么做，编译器会抛出异常、拒绝编译。但是Class文件来源不一定是由Java源码编译而来，他可以是使用二进制编辑器敲出Class文件，代码层面无法做到的事情，在Class文件中，至少是可以表达出来相关的语义，甚至可以包含Java虚拟机规范不允许的操作指令。因此验证阶段很有必要，而且必须严谨。</p>
<p><strong>验证流程：</strong></p>
<ol>
<li><p>文件格式验证</p>
<p>验证字节流是否符合Class文件格式的规范，并且能被当前版本虚拟机处理。包括但不限于以下验证点：</p>
<ul>
<li>是否以魔数0xCAFEBABE开头</li>
<li>主、次版本号是否在当前Java虚拟机接收范围内</li>
<li>常量池的常量中，是否由不被支持的常量类型（检查常量tag标志）</li>
<li>指向常量的各个索引值是否指向不存在的常量或不符合类型的常量</li>
<li>CONSTANT_Utf8_info型的长两种是否有不符合UTF-8编码的数据</li>
<li>Class文件中各个部分及文件本身是否有被删除或附加的其他信息</li>
</ul>
</li>
<li><p>元数据验证</p>
<p>对字节码描述信息进行语义分析，以保证其描述的信息符合《Java虚拟机规范》的要求，这个阶段可能包括但不限于以下验证点：</p>
<ul>
<li>这个类是否有父类</li>
<li>这个类的父类是否继承了不允许被继承的类</li>
<li>如果这个类不是抽象类，是否实现了其父类或接口之中要求实现的所有方法</li>
<li>类中的字段、方法是否与父类产生矛盾</li>
</ul>
</li>
<li><p>字节码验证</p>
<p>最复杂的一个阶段，是为了通过数据流分析和控制流分析，确定程序语义是否合法的、符合逻辑的。对方法体进行分析</p>
<ul>
<li>保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作</li>
<li>保证任何跳转指令不会跳转到方法体以外的字节码指令上</li>
<li>保证方法体中的类型转换总是有效的</li>
</ul>
</li>
<li><p>符号引用验证</p>
<p>发生在将符号引用转化为直接引用的时候（解析），可以看作是对类自身以外的各种信息进行匹配性校验，如该类是否缺少或被禁止访问它以来的某些外部类、方法、字段等资源：</p>
<ul>
<li>符号引用中通过字符串描述的全限定名是否能找到</li>
<li>在指定类中是否存在符合方法的字段描述符及简单名称所描述的方法</li>
<li>符号引用中的类、字段、方法的可访问性是否被当前类访问</li>
</ul>
<p>此阶段主要目的是确保解析行为正常进行，对虚拟机类加载机制来说，此阶段非常重要，但却不是必须要执行的阶段。</p>
</li>
</ol>
<h2 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h2><p>准备阶段是正式为类中定义的变量（静态变量）分配内存并设置变量初始值阶段。</p>
<ul>
<li><strong>这里分配的只有类变量没有实例变量，实例变量将会在对象实例化时随着对象一起分配在Java堆中。</strong></li>
<li>一般情况下<strong>即使赋值语句中设置了类变量的值，准备阶段，也只赋予变量初始值，如：int=0，long=0L，object=null。</strong>原因是赋值指令putstatic是程序编译后存放于类构造器方法中的，所以真实赋值过程也需要到类的初始化阶段才能执行。</li>
<li>特殊情况下（<strong>类字段的字段属性表中存在ConstantValue属性</strong>）准备阶段变量值就会被初始化为ConstantValue属性所指向的初始值。一般为final修饰的字段。</li>
</ul>
<h2 id="解析"><a href="#解析" class="headerlink" title="解析"></a>解析</h2><p>是Java虚拟机将常量池内的引用符号替换为直接引用的过程。</p>
<ul>
<li><strong>符号引用：</strong>通过符号来描述所引用的目标，符号可以是任何形式的字面量。只要使用时能无歧义地定位到目标即可。符号引用与虚拟机内存布局无关，引用目标不一定是已经加载到虚拟机内存当中的内容。因为这时候的引用只是描述信息，尚未与引用对象地实例相关联，因此与物理地址无关。但各种虚拟机需要能接受相同的符号引用，因为符号引用的字面量形式明确定义在《Java虚拟机规范》的Class文件格式中。</li>
<li><strong>直接引用：</strong>可以直接指向目标的指针、相对偏移量或是一个能简介定位到目标的句柄。直接引用与虚拟机内存布局有关。目标对象此时已经完成了内存分配，并器直接或间接地可以指向这个内存地址。所以相同符号引用，在不同虚拟机上翻译出地直接引用一般不会相同。初始化</li>
</ul>
<p><strong>解析执行时机在《Java虚拟机规范》中没有规定，只要求了17个用于操作符引用的字节码指令执行之前，先对它们所使用的符号引用进行解析。</strong></p>
<p><strong>17个用于操作符引用的指令为：</strong>ane-warray、checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual、ldc、ldc_w、ldc2_w、multianewarray、new、putfield、putstatic。</p>
<p><strong>对方法和字段的访问，在解析阶段需要对可访问性进行检查。</strong></p>
<p>由于对同一符号引用进行多次解析请求是很常见的事情，所以在静态加载过程中，虚拟机实现可以对第一次解析结果进行缓存，从而避免解析动作重复进行。而且，无论是否进行了多次解析动作，需要保证在同一实体中的符号引用结果一致，从不同指令触发得到的解析结果应该相同。但在动态解析中不需要保证这一点。</p>
<h2 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h2><p>此阶段正式开始执行编写的程序代码。根据程序员编码制定的主观计划去初始化变量和其他资源。即初始化过程是执行类构造器方法的过程。</p>
<p><strong>&lt;clinit&gt;()方法执行细节：</strong></p>
<ul>
<li><p>&lt;clinit&gt;()方法是由编译器自动收集类中所有变量的赋值动作和静态语句块中的语句合并而成，收集顺序是由语句在源文件中出现的顺序决定的。静态语句块中只能访问到定义在静态语句块之前的变量，定义在它后面的变量，只能赋值，不能访问。</p>
</li>
<li><p>类构造器（&lt;clinit&gt;()方法）与实例构造器（&lt;init&gt;()方法）不同，不需要显式地调用父类构造器，Java虚拟机会保证子类类构造器执行前，父类类构造器已经执行完毕。所以Java虚拟机第一个执行的类构造器地类型肯定是java.lang.Object</p>
</li>
<li><p>由于父类类构造器先执行，意味着，父类中定义的静态语句块要优先于子类的变量赋值操作。</p>
</li>
<li><p>&lt;clinit&gt;()方法对于类或接口来说并不是必须的，如果一个类中没有静态语句块，也没有对变量的赋值操作，编译器可以不为这个而立生成&lt;clinit&gt;()方法。</p>
</li>
<li><p>接口中存在变量赋值语句，所以接口也会生成&lt;clinit&gt;()方法，与类的区别是，执行接口&lt;clinit&gt;()方法不需要先执行父接口地&lt;clinit&gt;()方法，只有当父接口定义的变量被使用时，父接口才会被初始化。接口实现类初始化时，也不会执行接口的&lt;clinit&gt;()方法。</p>
</li>
<li><p>Java虚拟机必须保证一个类的&lt;clinit&gt;()方法在多线程环境中被正确的加锁同步，只有一个线程去初始化一个类，其他线程需要阻塞等待。</p>
</li>
</ul>
<h1 id="类加载器"><a href="#类加载器" class="headerlink" title="类加载器"></a>类加载器</h1><p><strong>类在Java虚拟机中的唯一性，由加载它的类加载器和这个类本身一起共同确立。</strong></p>
<h2 id="三层类加载器"><a href="#三层类加载器" class="headerlink" title="三层类加载器"></a>三层类加载器</h2><h3 id="启动类加载器（Bootstrap-ClassLoader）"><a href="#启动类加载器（Bootstrap-ClassLoader）" class="headerlink" title="启动类加载器（Bootstrap ClassLoader）"></a>启动类加载器（Bootstrap ClassLoader）</h3><ul>
<li>负责加载存放在&lt;JAVA_HOME&gt;\lib目录，或被-Xbootclasspath参数所制定的路径中存放的，而且是Java虚拟机能够识别的（按照文件名识别，如：rt.jar、tools.jar）类库加载到虚拟机内存中。</li>
<li>启动类加载器无法被Java程序直接引用。</li>
</ul>
<h3 id="扩展类加载器"><a href="#扩展类加载器" class="headerlink" title="扩展类加载器"></a>扩展类加载器</h3><ul>
<li>这个类加载器是在类sun.misc.Launcher$ExtClassLoader中以Java代码的形式实现的。负责加载&lt;JAVA_HOME&gt;\lib\ext目录中或被java.ext.dirs系统变量所指定的路径中的所有类库。</li>
<li>可以直接在程序中使用扩展类加载器来加载Class文件。</li>
</ul>
<h3 id="应用程序类加载器（系统类加载器）"><a href="#应用程序类加载器（系统类加载器）" class="headerlink" title="应用程序类加载器（系统类加载器）"></a>应用程序类加载器（系统类加载器）</h3><ul>
<li>这个类加载器由sun.misc.Launcher$AppClassLoader来实现。是ClassLoader类中getSystemClassLoader()方法的返回值。负责加载用户类路径上所有的类库。</li>
<li>可以直接在代码中使用这个类加载器。</li>
</ul>
<h2 id="双亲委派模型"><a href="#双亲委派模型" class="headerlink" title="双亲委派模型"></a>双亲委派模型</h2><img src="/blog-page/2021/10/28/cd70ccfc5667/Parents_Delegation_Model.jpg" class="" width="540" title="This is Parents_Delegation_Model.jpg">

<p>除顶层的启动类加载器外，其余的类加载器都应有自己的父类加载器（非继承，一般使用组合关系复用父加载器的代码）。</p>
<p><strong>工作过程：</strong>如果一个类加载器收到了类加载的请求，首先不会自己去尝试加载这个类，而是把这个请求委派给父类加载器去完成，每个层次的类加载器都是如此。只有当父加载器反馈自己无法完成这个加载请求时，子加载器才会尝试自己去完成加载。</p>
<p><strong>优势：</strong>Java中的类随着它的类加载器一起具备了一种带有优先级的层级关系。相同类型不论放到哪个类加载器去加载，最总都委派给处于模型最顶端的类加载器去加载。保证加载到的类是同一个。</p>
<h2 id="三次打破双亲委派模型"><a href="#三次打破双亲委派模型" class="headerlink" title="三次打破双亲委派模型"></a>三次打破双亲委派模型</h2><ul>
<li>双亲委派模型的抽象概念是在JDK1.2中被引入，在这之前，如果想使用用户自定义的类加载器，需要进行兼容处理，无法以技术手段避免loadClass()被子类覆盖的可能性，因此JDK1.2之后的java.lang.ClassLoader中添加了protected方法findClass()，并引导用户编写类的加载逻辑时，尽可能重写这几个方法，而不是在loadClass中编码。如果父类loadClass()加载失败，会调用自己的findClass()方法完成加载。</li>
<li>JDNI服务需要调用由其他厂商实现并部署在应用程序下的ClassPath下的JNDI服务提供者接口（SPI）的代码。一般JDNI服务等（JDBC、JCE、JAXB、JBI）的接口都定义在rt.jar下，这里的类一般是使用BootstrapClassLoader先进行加载的，但此处会调用厂商实现的具体实现类，BootstrapClassLoader无法加载这些第三方类库中的类，因此这时候引入了<strong>线程上下文类加载器</strong>。这个类加载器通过java.lang.Thread类的setContextClassLoader()方法设置，如果创建线程时还未设置，它将会从父线程中继承一个，如果引用在全局范围内都没设置过的话，这个类加载器默认就是引用程序类加载器。</li>
<li>JDK9（jigsaw）（或JDK9之前的OSGi）的模块化，打破了双亲委派的原则。</li>
</ul>
<h1 id="Java模块化系统"><a href="#Java模块化系统" class="headerlink" title="Java模块化系统"></a>Java模块化系统</h1><h2 id="Java模块定义"><a href="#Java模块定义" class="headerlink" title="Java模块定义"></a>Java模块定义</h2><ul>
<li>依赖其他模块的列表</li>
<li>导出的包列表，即其他模块可以使用的列表。</li>
<li>开放的包列表，即其他模块可反射访问的模块的列表。</li>
<li>使用的服务列表</li>
<li>提供服务的实现列表</li>
</ul>
<h2 id="可配置封装隔离机制"><a href="#可配置封装隔离机制" class="headerlink" title="可配置封装隔离机制"></a>可配置封装隔离机制</h2><p><strong>基于类路径（ClassPath）来查找依赖的可靠性问题：</strong></p>
<ul>
<li>JDK9之前的类路径缺失了运行时依赖的类型，需要等到程序运行到发生该类型加载、连接时才会抛出运行的异常。</li>
<li>JDK9之后，若启用模块化封装，模块可以声明对其他模块的显式依赖，这样Java虚拟机可以在启动时验证应用程序开发阶段设定好的依赖关系在运行期是否完备，如果有缺失，直接启动失败。</li>
</ul>
<p><strong>跨JAR文件的public类型的可访问性问题：</strong></p>
<ul>
<li>JDK9中的public类型不在意味着程序所有地方的代码都可以随意访问到它们，模块提供了更精细的可访问控制，必须明确声明其中哪一块的public类型可以被其他哪些模块访问，这种访问控制也主要在类加载过程中完成。</li>
</ul>
<h2 id="模块的兼容性"><a href="#模块的兼容性" class="headerlink" title="模块的兼容性"></a>模块的兼容性</h2><h3 id="向后兼容性"><a href="#向后兼容性" class="headerlink" title="向后兼容性"></a>向后兼容性</h3><p><strong>模块路径：</strong>某类库到底时模块还是传统的JAR包，取决于它存放在哪种路径上。存放在类路径上的JAR文件，无论其中是否包含模块化信息（module-info.class）都会被当作传统JAR包来对待。只要放在模块路径上的JAR文件，即使没有JMOD后缀，甚至不包含module-info.class文件，也仍被当作一个模块对待。</p>
<p><strong>兼容规则：</strong></p>
<ul>
<li>JAR文件在类路径的访问规则：所有类路径下的JAR文件及其他资源，都被视为自动打包在一个匿名模块里，这个模块几乎是没有任何隔离的。</li>
<li>模块在模块路径的访问规则：模块下的具名模块，只能访问到它以来定义中列明以来的模块和包，匿名模块里所有的内容对具名模块来说都是不可见的。</li>
<li>JAR文件在模块路径的访问规则：传统JAR文件放置在模块路径上，他会变成一个自动模块。尽管不包含module-info.class，但自动模块将默认依赖于整个模块路径中的所有模块，因此可访问到所有模块导出的包，自动模块也默认导出自己所有的包。</li>
</ul>
<h3 id="自身兼容性"><a href="#自身兼容性" class="headerlink" title="自身兼容性"></a>自身兼容性</h3><p><strong>模块间的管理和兼容性问题：</strong>如果同一个模块发行了多个不同版本，只能由开发者在编译打包时人工选择好正确版本的模块保证依赖的正确性。Java模块化系统目前不支持在模块定义中加入版本号来管理和约束依赖，本身也不支持多版本号的概念和版本选择功能。</p>
<h3 id="模块化下的类加载器"><a href="#模块化下的类加载器" class="headerlink" title="模块化下的类加载器"></a>模块化下的类加载器</h3><p><strong>变动</strong></p>
<ul>
<li>拓展类加载器被平台类加载器（platform Class Loader）取代。</li>
<li>平台类加载器和应用类加载器都不再派生自java.net.URLClassLoader。启动类加载器、平台类加载器、应用类加载器全都继承于jdk.internal.loader.BuiltinClassLoader。</li>
<li>增加了BootClassLoader，但原来获取启动类加载器的场景中，获取ClassLoader仍返回null，不会得到BootClassLoader实例。</li>
<li>类加载的委派关系发生变动。平台及应用程序类加载器收到类加载请求时，在委派给父加载器加载前，要先判断该类能否归属到某一个系统块中，如果可以，找到这样的归属关系，要优先委派给负责那个模块的加载器完成加载。</li>
</ul>
<p><strong>三个类加载器负责加载的模块</strong></p>
<ul>
<li><p><strong>启动类加载器</strong></p>
<p>java.base</p>
<p>java.security.sasl</p>
<p>java.datatransfer</p>
<p>java.desktop</p>
<p>java.httpserver</p>
<p>java.instrument</p>
<p>jdk.internal.vm.ci</p>
<p>java.logging</p>
<p>jdk.management</p>
<p>java.management</p>
<p>jdk.management.agent</p>
<p>java.management.rmi</p>
<p>jdk.naming.rmi</p>
<p>java.naming</p>
<p>java.prefs</p>
<p>java.rmi</p>
<p>jdk.unsupported</p>
</li>
<li><p><strong>平台类加载器</strong></p>
<p>java.activation*</p>
<p>jdk.accessibility</p>
<p>java.compiler*</p>
<p>jdk.charsets</p>
<p>java.corba*</p>
<p>jdk.crypto.cryptoki</p>
<p>java.scripting</p>
<p>jdk.crypto.ec</p>
<p>java.se</p>
<p>jdk.dynalink</p>
<p>java.se.ee</p>
<p>jdk.incubator.httpclient</p>
<p>java.security.jgss</p>
<p>jdk.internal.vm.compiler*</p>
<p>java.smartcardio</p>
<p>jdk.jsobjec</p>
<p>java.sql</p>
<p>jdk.localedata</p>
<p>java.sql.rowset</p>
<p>jdk.naming.dns</p>
<p>java.transaction*</p>
<p>jdk.scripting.nashorn</p>
<p>java.xml.bind*</p>
<p>jdk.security.auth</p>
<p>java.xml.crypto</p>
<p>jdk.security.jgss</p>
<p>java.xml.ws*</p>
<p>jdk.xml.dom</p>
<p>java.xml.ws.annotation*</p>
<p>jdk.zipfs</p>
</li>
<li><p><strong>应用类加载器</strong></p>
<p>jdk.aot</p>
<p>jdk.attach</p>
<p>jdk.compiler</p>
<p>jdk.jdwp.agent</p>
<p>jdk.editpad</p>
<p>jdk.hotspot.agent</p>
<p>jdk.jshell</p>
<p>jdk.internal.ed</p>
<p>jdk.jstatd</p>
<p>jdk.internal.jvmstat</p>
<p>jdk.internal.le</p>
<p>jdk.policytool</p>
<p>jdk.internal.opt</p>
<p>jdk.jartool</p>
<p>jdk.scripting.nashorn.shell</p>
<p>jdk.javadoc</p>
<p>jdk.xml.bind*</p>
<p>jdk.jcmd</p>
<p>jdk.xml.ws*</p>
<p>jdk.jconsole</p>
</li>
</ul>
 
      <!-- reward -->
      
      <div id="reword-out">
        <div id="reward-btn">
          打赏
        </div>
      </div>
      
    </div>
    

    <!-- copyright -->
    
    <div class="declare">
      <ul class="post-copyright">
        <li>
          <i class="ri-copyright-line"></i>
          <strong>版权声明： </strong>
          
          本博客所有文章除特别声明外，著作权归作者所有。转载请注明出处！
          
        </li>
      </ul>
    </div>
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=http://acexxs.gitee.io/blog-page/2021/10/28/cd70ccfc5667.html" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/blog-page/tags/ClassLoader/" rel="tag">ClassLoader</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/blog-page/tags/JVM/" rel="tag">JVM</a></li><li class="article-tag-list-item"><a class="article-tag-list-link" href="/blog-page/tags/Java/" rel="tag">Java</a></li></ul>

    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/blog-page/2021/10/28/b519104857db.html" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            深入理解JVM（九）虚拟机执行引擎
          
        </div>
      </a>
    
    
      <a href="/blog-page/2021/10/28/c045633ac839.html" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">深入理解JVM（七）Java类文件结构</div>
      </a>
    
  </nav>

   
<!-- valine评论 -->
<div id="vcomments-box">
  <div id="vcomments"></div>
</div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/valine@1.4.14/dist/Valine.min.js"></script>
<script>
  new Valine({
    el: "#vcomments",
    app_id: "4Gx0dzptNxW0Nb4xekYQLQF1-gzGzoHsz",
    app_key: "nvoM2TGGO7QpNlr3vAy3DQyg",
    path: window.location.pathname,
    avatar: "monsterid",
    placeholder: "欢迎一起探讨交流~",
    recordIP: true,
  });
  const infoEle = document.querySelector("#vcomments .info");
  if (infoEle && infoEle.childNodes && infoEle.childNodes.length > 0) {
    infoEle.childNodes.forEach(function (item) {
      item.parentNode.removeChild(item);
    });
  }
</script>
<style>
  #vcomments-box {
    padding: 5px 30px;
  }

  @media screen and (max-width: 800px) {
    #vcomments-box {
      padding: 5px 0px;
    }
  }

  #vcomments-box #vcomments {
    background-color: #fff;
  }

  .v .vlist .vcard .vh {
    padding-right: 20px;
  }

  .v .vlist .vcard {
    padding-left: 10px;
  }
</style>

 
   
     
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2021
        <i class="ri-heart-fill heart_icon"></i> ACEXS
      </li>
    </ul>
    <ul>
      <li>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></span>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>    
    </main>
    <div class="float_btns">
      <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

    </div>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/blog-page/"><img src="/blog-page/images/ayer-side.svg" alt="ACEXS个人博客"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/blog-page/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/blog-page/archives">归档</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/blog-page/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/blog-page/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/blog-page/friends">友链</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/blog-page/about">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/blog-page/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>表扬一下吧~</p>
  <div class="reward-box">
    
    <div class="reward-item">
      <img class="reward-img" src="/blog-page/images/alipay.png">
      <span class="reward-type">支付宝</span>
    </div>
    
    
    <div class="reward-item">
      <img class="reward-img" src="/blog-page/images/wechat.png">
      <span class="reward-type">微信</span>
    </div>
    
  </div>
</div>
    
<script src="/blog-page/js/jquery-3.6.0.min.js"></script>
 
<script src="/blog-page/js/lazyload.min.js"></script>

<!-- Tocbot -->
 
<script src="/blog-page/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: ".tocbot",
    contentSelector: ".article-entry",
    headingSelector: "h1, h2, h3, h4, h5, h6",
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: "main",
    positionFixedSelector: ".tocbot",
    positionFixedClass: "is-position-fixed",
    fixedSidebarOffset: "auto",
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css"
/>
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/blog-page/dist/main.js"></script>

<!-- ImageViewer -->
 <!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script> 
<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->
 
<script src="/blog-page/js/busuanzi-2.3.pure.min.js"></script>
 
<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->
 
<link rel="stylesheet" href="/blog-page/css/clipboard.css">
 <script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>
 
<!-- CanvasBackground -->
 
<script src="/blog-page/js/dz.js"></script>
 
<script>
  if (window.mermaid) {
    mermaid.initialize({ theme: "forest" });
  }
</script>


    
    

  </div>
</body>

</html>